// Drummer
// by Andrea Bianchi
// andreabianchi.alsoplantsfly.com


/*
 Copyright (C) 2009, Andrea Bianchi
 
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/


#include "drumManager.h"
#include "screenManager.h"
#include "networkManager.h"
#include "doubleColor.h"
using namespace DS;

#include <string>



DrumManager* DrumManager::getInstance()
{	
	static DrumManager histnace;
	return &histnace;
}


DrumManager::DrumManager()
: drums_(0), editMode_(false), selected_(NO_SELECTION)
{
	// The circles are all set to invisible, but the initial color is pink
	for(u8 i=0; i<drumArraySize_; ++i)
	{
		circles_[i]= Circle(Coord(0,0),minCircleSize_,pink	,BLACK);
	}
}


void DrumManager::draw()
{
	for(u8 i=0; i<drums_; ++i)
	{
		circles_[i].draw(LOWER_SCREEN);
	}
}


// since there are only 6 instances maximum on screen, bouble sort will suffice
void DrumManager::sortDrums ()
{
	for (u8 i=0; i<drums_; ++i){
		for (u8 j=i; j<drums_; ++j){

			if ((circles_[i]==circles_[j]) && (i==selected_))
			{
				swapCircles(i,j);
			} else if (circles_[i]<circles_[j]){
				swapCircles(i,j);						
			}
		}
	}
}


void DrumManager::selectDrum (const Coord& pos)
{
	selected_=NO_SELECTION;
	for (u8 i=0; i<drums_; ++i){
		if (circles_[i].hitTest(pos))
		{
			selected_= i;
		}
	}
}


void DrumManager::beginCreateDrum (const Coord& pos)
{
	if (drums_==drumArraySize_) return;
	// there is space
	editMode_= true;
	circles_[drums_].setPosition(pos);
	circles_[drums_].setRadius(minCircleSize_);
	drums_++;

}

void DrumManager::endCreateDrum ()
{
	editMode_= false;
}


void DrumManager::press(){

	Coord stylus= Coord(Stylus.X, Stylus.Y); 	
	u8 prevId= selected_;
	selectDrum(stylus);

	// finalize if you click on a new location or if you released R or L
	if ((selected_!=prevId) || !(Pad.Held.L || Pad.Held.R))
	{
		finalizeCircle();
	}

	if (hasSelected()){

		if (Pad.Held.L || Pad.Held.R)
		{
			grab_= circles_[selected_].getPosition()-stylus;

			if (Pad.Held.B || Pad.Held.Down)
			{
				hitCircle();
				removeSelectedCircle();
				sortDrums();
				deselectDrum();
				ScreenManager::getInstance()->setBG(IDLE);
				return;
			}
		
		}else{
			
			if (Pad.Held.B || Pad.Held.Down)
			{
				circles_[selected_].decremnetVelocity();
				return;

			} if (Pad.Held.X || Pad.Held.Up)
			{
				circles_[selected_].incrementVelocity();
				return;

			}else
			{
				// hit
				ScreenManager::getInstance()->setBG(HIT);	
			}
		}
		hitCircle();

	}else{

		if (Pad.Held.L || Pad.Held.R)
		{	
			if (Pad.Held.Down|| Pad.Held.B) return;

			// nothing selectable, hence create one circle
			beginCreateDrum(stylus);
			selected_= drums_-1;

		}
	}
}


void DrumManager::release(){
	// screen idle
	ScreenManager::getInstance()->setBG(IDLE);
	releaseCircle();

	grab_=Coord(0,0);

	if (Pad.Held.L || Pad.Held.R)
	{
		return;
	}
	finalizeCircle();
	deselectDrum ();
}


void DrumManager::drag(){
	
	if (!hasSelected()) return;
	Coord stylus= Coord(Stylus.X, Stylus.Y);

	
 	if (Pad.Held.L || Pad.Held.R){
		moveDrumsTo(stylus+grab_);
		selectBackground();
		selectAllCirclesColor();

 	}else{
 		if (isEditMode())
 		{
			grab_= circles_[selected_].getPosition()-stylus;
 			editDrumSize(stylus);
			selectBackground();
			selectAllCirclesColor();
		}
	}
}
	


void DrumManager::removeCircle (u8 index)
{
	// swap with the last one and then decrement the counter
	swapCircles(index, drums_-1);
	drums_--;
}

void DrumManager::removeSelectedCircle ()
{
	// swap with the last one and then decrement the counter
	circles_[selected_].resetVelocity();
	swapCircles(selected_, drums_-1);
	drums_--;
	deselectDrum();
}

void DrumManager::moveDrumsTo (const Coord& pos)
{
	circles_[selected_].setPosition(pos);
}

void DrumManager::editDrumSize (const Coord& pos)
{
	Coord orig= circles_[selected_].getPosition();
	u16 radius=  PA_TrueDistance (orig.x_, orig.y_, pos.x_, pos.y_);
	radius= (radius<minCircleSize_)? minCircleSize_:radius;
	circles_[selected_].setRadius(radius);
	sortDrums();
}

void DrumManager::swapCircles (u8 i, u8 j)
{
	if (i==j) return;
	// swap
	Circle temp= circles_[i];
	circles_[i]= circles_[j];
	circles_[j]= temp;

	// swap the selection, if any
	if (selected_== i) selected_=j;
	else if (selected_== j) selected_=i;
}


bool DrumManager::isHiHat (u8 circleNumber) const
{
	u8 sel=0;
	Coord pos= circles_[circleNumber].getPosition();

	for (u8 i=0; i<drums_; ++i){
		if (i==circleNumber) continue;
		if (circles_[i].hitTest(pos))
		{
			// check if selected is smaller
			if (circles_[circleNumber].getRadius() < circles_[i].getRadius())
				return true;
		}
	}
	return false;
}



void DrumManager::finalizeCircle()
{
	//set all the selected drums on front and then order
	sortDrums();
	// end edit mode and clean up selection
	endCreateDrum();
}


void DrumManager::selectBackground()
{
	DrumPart part= getSelectedCircleDrumPart();
	Background bg;

	switch(part)
	{
	case BASSDRUM1:
	case BASSDRUM2: bg= BASSDRUM; break;
	case SNARE1:
	case SNARE2: bg= SNARE; break;
	case CRASH1:
	case CRASH2: bg = CRASH; break;
	case HIHAT1:
	case HIHAT2: bg= HIHAT; break;
	case TOM1:
	case TOM2:
	case TOM3:
	case TOM4: bg= TOM; break;
	default: bg= IDLE;
	}

	ScreenManager::getInstance()->setBG(bg);
}


DrumPart DrumManager::getSelectedCircleDrumPart() const
{
	getDrumPart(selected_);
}

// assume the circle number passed to be in the right range!!
DrumPart DrumManager::getDrumPart(u8 circleNumber) const
{
	u8 size= circles_[circleNumber].getRadius();

	if (isHiHat(circleNumber)){
		if (size > 50) 
			return HIHAT1;
		else
			return HIHAT2;

	}else{
		
		
		if (size > 110) 
			return BASSDRUM1;
		else if (size > 90)
			return BASSDRUM2;

		else if (size>75) 
			return SNARE1;
		else if (size>60) 
			return SNARE2;

		else if (size>50) 
			return CRASH1;
		else if (size>40) 
			return CRASH2;

		else if (size>30)
			return TOM1;
		else if (size>20)
			return TOM2;
		else if (size>10)
			return TOM3;
		else
			return TOM4;
	}
}

void DrumManager::selectAllCirclesColor()
{
	for (u8 i=0; i<drums_; ++i){
		selectCircleColor(i);
	}
}

void DrumManager::selectSelectedCircleColor()
{
	selectCircleColor(selected_);
}

// assume parameter in right range
void DrumManager::selectCircleColor(u8 circleNumber)
{
	DrumPart part= getDrumPart(circleNumber);
	DoubleColor col;

	switch(part)
	{
	case BASSDRUM1:
	case BASSDRUM2: col= blue; break;
	case SNARE1:
	case SNARE2: col= orange ; break;
	case CRASH1:
	case CRASH2: col = red; break;
	case HIHAT1:
	case HIHAT2: col= green; break;
	case TOM1:
	case TOM2:
	case TOM3:
	case TOM4: col= pink; break;
	default: col= grey;
	}

	circles_[circleNumber].setColor(col);
}


void DrumManager::hitCircle()
{
	DrumPart part= getSelectedCircleDrumPart();
	circles_[selected_].setColor(yellow);
	NetworkManager::getInstance()->sendMessage(part, circles_[selected_].getVelocity());	
}

void DrumManager::releaseCircle()
{
	selectSelectedCircleColor();
	NetworkManager::getInstance()->sendMessage(SILENCE);
}